﻿#region License
/*
 **************************************************************
 *  Author: Rick Strahl 
 *          © West Wind Technologies, 2008 2011
 *          http://www.west-wind.com/
 * 
 * Created: 09/04/2008
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 **************************************************************  
*/
#endregion

using System;
using System.Web;
using System.IO;

using System.Collections.Generic;
using System.Web.Script.Serialization;
using NetFramework.Web.Ajax.Utilities;

namespace NetFramework.Web.Ajax
{

    /// <summary>
    /// This is the core implementation of the JSON callback method handler that 
    /// picks up POST data from the request and uses it to call the actual callback
    ///  method on the specified object and return the results back as JSON.
    /// 
    /// This processor is generic and can be used easily from anywhere that needs 
    /// to feed back JSON data from a method callback, simply calling the 
    /// ProcessCallbackMethod() with an object that contains methods that are 
    /// marked up with the [CallbackMethod] attribute.
    /// 
    /// For example, wwCallbackHandler simply forwards all processing like this:
    /// 
    /// &lt;&lt;code lang="C#"&gt;&gt;public void ProcessRequest(HttpContext 
    /// context)
    /// {
    ///     // Pass off to the worker Callback Processor
    ///     ICallbackMethodProcessor processor = new JsonCallbackMethodProcessor();
    /// 
    ///     // Process the inbound request and execute it on this
    ///     // Http Handler's methods
    ///     processor.ProcessCallbackMethodCall(this);
    /// }&lt;&lt;/code&gt;&gt;
    /// 
    /// This processor is expected to execute in an environment where 
    /// HttpContext.Current is available and where POST data is available to 
    /// describe the incoming parameter data and method to call.
    /// </summary>
    public class JsonCallbackMethodProcessor : ICallbackMethodProcessor
    {
        
        /// <summary>
        /// JSONP method parameter value if provided
        /// </summary>
        protected internal string JsonPMethod
        {
            get { return _JsonPMethod; }
            set { _JsonPMethod = value; }
        }
        private string _JsonPMethod = null;



        /// <summary>
        /// Generic method that handles processing a Callback request by routing to
        /// a method in a provided target object.
        /// 
        /// </summary>
        /// <param name="target">The target object that is to be called. If null this is used</param>
        public void ProcessCallbackMethodCall(object target, string methodToCall)
        {
            if (target == null)
                target = this;

            HttpRequest Request = HttpContext.Current.Request;
            HttpResponse Response = HttpContext.Current.Response;

            Response.Charset = "UTF-8";
            Response.ContentType = ControlResources.STR_JsonContentType;

            if (string.IsNullOrEmpty(methodToCall))
                methodToCall = Request.Params["Method"];

            if (string.IsNullOrEmpty(methodToCall))
            {
                WriteErrorResponse("No method to call specified.", null);
                return;
            }

            CallbackMethodProcessorHelper helper = new CallbackMethodProcessorHelper(this);
            
            List<string> parameterList = null;
            
            string contentType = Request.ContentType.ToLower();
            // Allow for a single JSON object to be POSTed rather than POST variables
            if (contentType.Contains(ControlResources.STR_Json) ||
                contentType.Contains(ControlResources.STR_JavaScrip))
            {
                // Pass a Parameter List with our JSON encoded parameters
                parameterList = new List<string>();

                if (Request.ContentLength > 0)
                {
                    // Pick up single unencoded JSON parameter
                    StreamReader sr = new StreamReader(Request.InputStream);
                    string singleParm = sr.ReadToEnd();
                    sr.Close();
                    sr.Dispose();

                    if (!string.IsNullOrEmpty(singleParm))
                        parameterList.Add(singleParm);
                }
            }
            
            object result = null;
            string stringResult = null;
            CallbackMethodAttribute attr = new CallbackMethodAttribute();
            try
            {
                if (parameterList != null)
                    // use the supplied parameter list
                    result = helper.ExecuteMethod(methodToCall,target, parameterList.ToArray(),
                                        CallbackMethodParameterType.Json,ref attr);
                else
                    // grab the info out of QueryString Values or POST buffer during parameter parsing 
                    // for optimization
                    result = helper.ExecuteMethod(methodToCall, target, null, 
                                                  CallbackMethodParameterType.Json, ref attr);
            }
            catch (Exception ex)
            {
                Exception activeException = DebugUtils.GetInnerMostException(ex);
                WriteErrorResponse(activeException.Message,
                                  ( HttpContext.Current.IsDebuggingEnabled ? ex.StackTrace : null ) );
                return;
            }

            // Special return type handling: Stream, Bitmap, byte[] and raw string results
            // are converted and returned directly
            HandleSpecialReturnTypes(result, attr, Request, Response);
            
            // Standard json formatting            
            try
            {
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                stringResult = serializer.Serialize(result);
            }
            catch (Exception ex)
            {
                WriteErrorResponse(ex.Message, HttpContext.Current.IsDebuggingEnabled ? ex.StackTrace : null);
                return;
            }

            Response.Write(stringResult);
            Response.End();
        }

        /// <summary>
        /// This method handles special return types from callback methods
        /// by examining the return type or the ResultFormat query string.
        /// 
        /// Checks are performed for:
        /// Stream, Bitmap, byte[] and raw string output
        /// </summary>
        /// <param name="Result"></param>
        /// <param name="callbackAttribute"></param>
        /// <param name="Request"></param>
        /// <param name="Response"></param>
        void HandleSpecialReturnTypes(object Result, CallbackMethodAttribute callbackAttribute, HttpRequest Request, HttpResponse Response)
        {
            string format = (Request.Params["Format"] ?? "").ToLower();
            //string format = (Request.Params["ResultFormat"] ?? "").ToLower();

            // [Callback(ContentType="text/html"] override
            if (!string.IsNullOrEmpty(callbackAttribute.ContentType))
                Response.ContentType = callbackAttribute.ContentType;
           
            if ((format == "string" || callbackAttribute.ReturnAsRawString) && Result.GetType() == typeof(string))
            {
                if (!string.IsNullOrEmpty(callbackAttribute.ContentType))
                    Response.ContentType = callbackAttribute.ContentType;

                Response.Write(Result as string);
                Response.End();
            }
        }


        /// <summary>
        /// Generic method that handles processing a Callback request by routing to
        /// a method in a provided target object.
        /// 
        /// This version doesn't pass in the method name but retrieves it from the
        /// POST data or query string.
        /// </summary>
        /// <param name="target"></param>
        public void ProcessCallbackMethodCall(object target)
        {
            ProcessCallbackMethodCall(target, null);
        }

        /// <summary>
        /// Returns an error response to the client from a callback. Code
        /// should exit after this call.
        /// </summary>
        /// <param name="ErrorMessage"></param>
        public void WriteErrorResponse(string errorMessage, string stackTrace)
        {
            CallbackException Error = new CallbackException();
            Error.message = errorMessage;
            Error.isCallbackError = true;
            Error.stackTrace = stackTrace;

            JavaScriptSerializer Serializer = new JavaScriptSerializer();         
            string result = Serializer.Serialize(Error);

            if (!string.IsNullOrEmpty(JsonPMethod))
                result = JsonPMethod + "( " + result + " );";

            HttpResponse Response = HttpContext.Current.Response;
            Response.ContentType = ControlResources.STR_JsonContentType;

            Response.TrySkipIisCustomErrors = true;
            
            // override status code but only if it wasn't set already
            if (Response.StatusCode == 200)
                Response.StatusCode = 500;
            
            Response.Write(result);
            
            Response.End();
        }
    }
}
